﻿using Microsoft.Extensions.Caching.Memory;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching;
using Microsoft.Extensions.Caching.Distributed;
using JT808.Protocol.Extensions.JTActiveSafety.Metadata;
using JTActiveSafety.Gateway;

namespace Demo.AttachFileService.Models
{
    public class DistributedCacheService
    {
        private readonly IDistributedCache _memoryCache;

        public DistributedCacheService(IDistributedCache memoryCache)
        {
            _memoryCache = memoryCache;
        }

        public List<Node> Add(string fileName, uint fileSize, uint Offset, uint Length, byte[] Bodies)
        {
            var options = new DistributedCacheEntryOptions()
            {
                //AbsoluteExpiration = DateTime.Now.AddMinutes(30),//绝对过期时间
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(20), //相对当前时间
                SlidingExpiration = TimeSpan.FromMinutes(10), //滑动过期，在被删除之前可以处于非活动状态（例如，未访问）的时间
            };

            var a = _memoryCache.Get(fileName);
            var cacheItem = new CacheItem();
            if (a == null)
            {
                cacheItem.Nodes.Add(new Node() { Offset = Offset, Length = Length });
                cacheItem.Size = Length;
            }
            else
            {
                cacheItem = Deserialize(a);
                cacheItem.Nodes.Add(new Node() { Offset = Offset, Length = Length });
                cacheItem.Size += Length;
            }
            if (IsOk(fileSize, cacheItem))
            {
                return cacheItem.Nodes;
            }
            else
            {
                _memoryCache.Set(fileName, Serialize(cacheItem), options);
                _memoryCache.Set(fileName + Offset + Length, Bodies, options);
                return null;
            }
        }

        public List<Node> QueryDataKeys(string fileName)
        {
            var a = _memoryCache.Get(fileName);
            if (a == null)
            {
                return null;
            }
            else
            {
                return Deserialize(a).Nodes;
            }
        }
        private static bool IsOk(uint fileSize, CacheItem item)
        {
            if (fileSize > item.Size)
            {
                return false;
            }

            var result = new List<Node>();
            if (CheckLinkedListMissingNodes.TryCheck(item.Nodes.OrderBy(c => c.Offset).ToList(), fileSize, ref result))
            {
                if (result.Count == 0)
                    return true;
            }
            return false;
        }

        public byte[] GetFileData(string fileName, uint Offset, uint Length)
        {
            return _memoryCache.Get(fileName + Offset + Length);
        }

        public void Remove(string fileName)
        {
            _memoryCache.Remove(fileName);
        }
        public void Remove(string fileName, List<Node> nodes)
        {
            foreach (var item in nodes)
            {
                _memoryCache.Remove(fileName + item.Offset + item.Length);
            }
        }

        private static byte[] Serialize(CacheItem input)
        {
            var len = input.Nodes.Count * 8 + 4;
            var index = 0;
            Span<byte> writer = stackalloc byte[len];
            BinaryPrimitives.WriteUInt32BigEndian(writer.Slice(index), input.Size);
            index += 4;
            for (int i = 0; i < input.Nodes.Count; i++)
            {
                BinaryPrimitives.WriteUInt32BigEndian(writer.Slice(index), input.Nodes[i].Offset);
                index += 4;
                BinaryPrimitives.WriteUInt32BigEndian(writer.Slice(index), input.Nodes[i].Length);
                index += 4;
            }
            return writer.ToArray();
        }

        private static CacheItem Deserialize(ReadOnlySpan<byte> span)
        {
            int index = 0;
            var item = new CacheItem();
            item.Size = BinaryPrimitives.ReadUInt32BigEndian(span.Slice(index, 4));
            index += 4;
            do
            {
                var offset = BinaryPrimitives.ReadUInt32BigEndian(span.Slice(index, 4));
                index += 4;
                var length = BinaryPrimitives.ReadUInt32BigEndian(span.Slice(index, 4));
                index += 4;

                item.Nodes.Add(new Node() { Offset = offset, Length = length });
            } while (index < span.Length);
            return item;
        }
        private class CacheItem
        {
            public uint Size { get; set; }
            public List<Node> Nodes { get; set; } = new List<Node>();
        }
    }
    public class CacheItem2 : Node
    {
        public byte[] Bodies { get; set; }
    }
}
